package at.momberban.game.me;

/*
 * Game.java 
 * 
 * author: The MomberBan Team 
 *    date: 12.09.2008
 *    
 */

import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;

import javax.microedition.lcdui.Displayable;

/**
 * the game you know?
 * 
 * @author patchinsky
 */
public class Game {
    private static final byte STATE_PREPARING = 0x01;

    private static final byte STATE_RUNNING = 0x02;

    private static final byte STATE_FINISHED = 0x03;
    
    private static final byte STATE_TERMINATED = 0x04;

    private MomberBanCanvas canvas;

    private Logic logic;

    private NetworkManager networkManager;

    private EventGenerator eventGenerator;

    private SynchronizedQueue eventQueue = Event.getEventQueue();

    private GameMap map;

    private Hashtable items;

    private Player player;

    private Player winner;

    private Hashtable players;

    private Hashtable bombs;

    private byte state;

    private boolean isHost;

    private EventListener inGameListener;

    /**
     * set the current game state
     * 
     * @param mode
     *            control
     */
    private final void setState(byte mode) {
        this.state = mode;

        switch (this.state) {
            case STATE_PREPARING: // prepare the game
                // the network event listener
                this.eventGenerator = new EventGenerator();
                this.networkManager.setEventListener(this.eventGenerator);

                // create a new thread for handling pre-game events
                new Thread(new GameListener()).start();

                if (this.players == null) { // first time
                    this.players = new Hashtable();

                    // host is player one
                    if (isHost) {
                        this.player = new Player(1);
                        this.players.put(IntegerCache.valueOf(1), this.player);
                    }
                } else { // another try
                    Enumeration i2 = this.players.keys();
                    while (i2.hasMoreElements()) {
                        Integer key = (Integer) i2.nextElement();
                        Player p = (Player) this.players.get(key);
                        p.revitalize();
                    }

                    Logger.log("revitalize complete");
                }

                if (isHost) {
                    this.logic = new ServerLogic(this);
                } else {
                    this.logic = new ClientLogic(this);
                }
                

                try {
                    map.readMap();
                } catch (IOException e) {
                    Logger.log("could not read map: " + map.getTitle());
                }

                break;
            case STATE_RUNNING: // init the game
                this.canvas = new MomberBanCanvas(this);

                this.bombs = new Hashtable();
                this.items = new Hashtable();

                this.logic.startGame();

                new Thread(this.canvas).start();
                
                notifyListener();
                break;
            case STATE_TERMINATED:
            case STATE_FINISHED:
                if (this.canvas != null) {
                    this.canvas.setRunning(false);
                }
                
                notifyListener();
                break;
        }
    }

    /**
     * 
     */
    private void notifyListener() {
        if (this.inGameListener == null) {
            Logger.log("no-one is interested");
        } else {
            Logger.log("control back to midlet");
            this.inGameListener.wakeUp();
        }
    }

    /**
     * Getter of the property <tt>bombs</tt>
     * 
     * @return Returns the bombs.
     */
    public final Hashtable getBombs() {
        return bombs;
    }

    /**
     * Setter of the property <tt>bombs</tt>
     * 
     * @param bombs
     *            The bombs to set.
     */
    public final void setBombs(Hashtable bombs) {
        this.bombs = bombs;
    }

    /**
     * Getter of the property <tt>map</tt>
     * 
     * @return Returns the map.
     */

    public final GameMap getMap() {
        return map;
    }

    /**
     * Setter of the property <tt>map</tt>
     * 
     * @param map
     *            The map to set.
     */
    public final void setMap(GameMap map) {
        this.map = map;
    }

    /**
     * Getter of the property <tt>networkManager</tt>
     * 
     * @return Returns the networkManager.
     */

    public final NetworkManager getNetworkManager() {
        return networkManager;
    }

    /**
     * Setter of the property <tt>networkManager</tt>
     * 
     * @param networkManager
     *            The networkManager to set.
     */
    public final void setNetworkManager(NetworkManager networkManager) {
        this.networkManager = networkManager;
    }

    /**
     * return myself
     * 
     * @return player
     */
    public final Player getPlayer() {
        return player;
    }

    /**
     * @param me
     */
    public final void setPlayer(Player me) {
        this.player = me;
    }

    /**
     * return all players
     * 
     * @return players
     */
    public final Hashtable getPlayers() {
        return players;
    }

    /**
     * return the game as a displayable for the midlet
     * 
     * @return canvas
     */
    public final Displayable getCanvas() {
        return canvas;
    }

    /**
     * return the current used logic
     * 
     * @return logic
     */
    public final Logic getLogic() {
        return logic;
    }

    /**
     * @return winner
     */
    public final Player getWinner() {
        return winner;
    }
    
    /**
     * @return items
     */
    public Hashtable getItems() {
        return items;
    }

    /**
     * @param isHost
     *            to set
     */
    public final void setHost(boolean isHost) {
        Logger.log("##################################");
        Logger.log((isHost ? "[[HOST]]" : "[[CLIENT]]"));
        Logger.log("##################################");
        this.isHost = isHost;
    }

    /**
     * try to connect to the server
     * 
     * @param id
     *            the connection id
     * @return
     */
    public final boolean connect(int id) {
        if (this.isHost) {
            throw new RuntimeException("cannot connect as a server");
        }
        
        ((ClientLogic) this.logic).connect(id);
        Logger.log("logged in: " +  ((ClientLogic) this.logic).isLoggedIn());
        return ((ClientLogic) this.logic).isLoggedIn();
    }

    /**
     * start the server listener
     */
    public final void listen() {
        if (!this.isHost) {
            throw new RuntimeException("cannot listen as a client");
        }

        try {
            ((ServerNetworkManager) networkManager).listen();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * discover the servers
     * 
     * @param noCache
     *            don't cache
     * @return the discovered servers
     */
    public final String[] discover(boolean noCache) {
        if (this.isHost) {
            throw new RuntimeException("cannot discover as a server");
        }

        String[] ret = null;
        try {
            ret = ((ClientNetworkManager) networkManager).discover(noCache);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return ret;
    }

    /**
     * start the game
     */
    public final void start() {
        setState(STATE_RUNNING);
    }

    /**
     * prepare for a new game
     */
    public final void prepare() {
        setState(STATE_PREPARING);
    }

    /**
     * disconnect
     */
    public synchronized final void disconnect() {
        this.networkManager.disconnect();
    }

    /**
     * return if the game is prepared
     * 
     * @return
     */
    public synchronized final boolean isPrepared() {
        return (this.state == STATE_PREPARING);
    }

    /**
     * return if the game is started
     * 
     * @return
     */
    public synchronized final boolean isStarted() {
        return (this.state == STATE_RUNNING);
    }

    /**
     * @return if the game is finished normally
     */
    public synchronized final boolean isFinished() {
        return this.state == STATE_FINISHED;
    }
    
    /**
     * @return if the game terminated
     */
    public synchronized final boolean isTerminated() {
        return this.state == STATE_TERMINATED;
    }

    /**
     * @param p
     */
    public final void finish(Player p) {
        if (!isStarted()) {
            Logger.log("game not running. cannot finish");
        }

        this.winner = p;
        setState(STATE_FINISHED);
    }

    /**
     * @param listener
     */
    public void register(EventListener listener) {
        Logger.log("ingame-listener registered");
        this.inGameListener = listener;
    }
    
    /**
     * set the state to terminated
     */
    public void stop() {
        setState(STATE_TERMINATED);
    }

    /**
     * the game event listener
     * (pre-game, in-game, finish)
     */
    private class GameListener implements Runnable, EventListener {
        /**
         * constructor
         * 
         * @param g
         *            the game
         */
        public GameListener() {
            // the thread wants to be notified
            eventGenerator.register(this);
        }

        /**
         * run the event listener thread
         * 
         * @see java.lang.Runnable#run()
         */
        public void run() {
            Logger.log("game: pre-game event listener running");
            while (state == STATE_PREPARING) {
                delegateEvents();
            }

            // don't notify while in game
            Logger.log("game: pre-game event listener terminated");
            eventGenerator.unregister(this);
        }

        /**
         * handle incoming game preparation events
         */
        private synchronized final void delegateEvents() {
            while (eventQueue.isEmpty() && !isStarted()) {
                try {
                    wait();
                    Logger.log("game: new pre-game event");
                    logic.handleEvents();
                    inGameListener.wakeUp();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (NullPointerException e) {
                    e.printStackTrace();
                }
            }
        }

        /**
         * override
         * 
         * @see at.momberban.game.me.EventListener#wakeUp()
         */
        public synchronized final void wakeUp() {
            notifyAll();
        }

    }

}
